import sys
import os
import json
import time
import re
import requests
import threading
from queue import Queue
from urllib.parse import urljoin, urlparse
from bs4 import BeautifulSoup
import chardet
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
                             QLabel, QLineEdit, QPushButton, QTextEdit, QMessageBox,
                             QFileDialog, QTabWidget, QTreeWidget, QTreeWidgetItem,
                             QProgressBar, QComboBox, QCheckBox, QGroupBox, QFormLayout,
                             QSplitter, QMenu, QAction, QDialog, QGridLayout, QLabel,
                             QTextBrowser, QListWidget, QHeaderView, QFrame, QSizePolicy,
                             QToolBar, QStatusBar, QActionGroup, QToolButton, QDockWidget)
from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot, QObject, QDateTime
from PyQt5.QtGui import QColor, QFont, QIcon


class LogLevel:
    DEBUG = "DEBUG"
    INFO = "INFO"
    WARNING = "WARNING"
    ERROR = "ERROR"
    CRITICAL = "CRITICAL"


class Logger:
    """日志记录器，支持不同级别日志和输出到文件"""

    def __init__(self, log_widget=None, log_file=None):
        self.log_widget = log_widget
        self.log_file = log_file
        self.log_levels = {
            LogLevel.DEBUG: {"color": "#888888", "enabled": True},
            LogLevel.INFO: {"color": "#000000", "enabled": True},
            LogLevel.WARNING: {"color": "#FF8C00", "enabled": True},
            LogLevel.ERROR: {"color": "#FF0000", "enabled": True},
            LogLevel.CRITICAL: {"color": "#8B0000", "enabled": True}
        }

        # 创建日志文件
        if self.log_file:
            os.makedirs(os.path.dirname(self.log_file), exist_ok=True)
            with open(self.log_file, 'w', encoding='utf-8') as f:
                f.write(f"[{QDateTime.currentDateTime().toString('yyyy-MM-dd hh:mm:ss')}] 日志文件已创建\n")

        # 创建信号发射器
        self.signals = SignalEmitter()

    def set_log_widget(self, log_widget):
        """设置日志显示控件"""
        self.log_widget = log_widget

    def set_log_file(self, log_file):
        """设置日志文件"""
        self.log_file = log_file

        # 创建日志文件
        if self.log_file:
            os.makedirs(os.path.dirname(self.log_file), exist_ok=True)
            with open(self.log_file, 'w', encoding='utf-8') as f:
                f.write(f"[{QDateTime.currentDateTime().toString('yyyy-MM-dd hh:mm:ss')}] 日志文件已创建\n")

    def log(self, level, message):
        """记录日志"""
        if level not in self.log_levels or not self.log_levels[level]["enabled"]:
            return

        timestamp = QDateTime.currentDateTime().toString('yyyy-MM-dd hh:mm:ss')
        log_entry = f"[{timestamp}] [{level}] {message}"

        # 输出到文件
        if self.log_file:
            try:
                with open(self.log_file, 'a', encoding='utf-8') as f:
                    f.write(log_entry + '\n')
            except Exception as e:
                print(f"写入日志文件失败: {str(e)}")

        # 输出到界面
        if self.log_widget:
            # 使用信号发送日志消息
            self.signals.log_message.emit(level, log_entry)

    def debug(self, message):
        """记录调试日志"""
        self.log(LogLevel.DEBUG, message)

    def info(self, message):
        """记录信息日志"""
        self.log(LogLevel.INFO, message)

    def warning(self, message):
        """记录警告日志"""
        self.log(LogLevel.WARNING, message)

    def error(self, message):
        """记录错误日志"""
        self.log(LogLevel.ERROR, message)

    def critical(self, message):
        """记录严重错误日志"""
        self.log(LogLevel.CRITICAL, message)

    def set_level_enabled(self, level, enabled):
        """启用或禁用特定级别的日志"""
        if level in self.log_levels:
            self.log_levels[level]["enabled"] = enabled


class SignalEmitter(QObject):
    """信号发射器，用于在线程间通信"""
    progress_updated = pyqtSignal(int)
    comment_found = pyqtSignal(dict)
    attachment_found = pyqtSignal(dict)
    crawl_completed = pyqtSignal(bool, str)
    log_message = pyqtSignal(str, str)  # 日志消息, 日志级别
    download_progress = pyqtSignal(str, int, int)  # 文件名, 已下载大小, 总大小
    download_completed = pyqtSignal(str, bool, str)  # 文件名, 是否成功, 消息


class CrawlerThread(threading.Thread):
    """爬虫线程，负责网页爬取和解析"""

    def __init__(self, url, comment_selectors=None, attachment_selectors=None, max_pages=10, detect_mode=False,
                 logger=None):
        super().__init__()
        self.url = url
        self.max_pages = max_pages
        self.detect_mode = detect_mode
        self.running = True
        self.headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
        }

        # 默认的评论选择器集合，覆盖常见的评论区结构
        self.default_comment_selectors = [
            'div.comment', 'div.reply', 'div.comment-item', 'div.comment-body',
            'div.post-message', 'div.thread-message', 'div.comment-content',
            'div.message', 'div.review', 'div.comment-text', 'div.cmt-content',
            'div.comment-detail', 'div.comment-box', 'div.comment-wrapper'
        ]

        # 默认的附件选择器集合，针对压缩文件和常见文档
        self.default_attachment_selectors = [
            'a[href$=".zip"]', 'a[href$=".rar"]', 'a[href$=".7z"]', 'a[href$=".tar"]',
            'a[href$=".gz"]', 'a[href$=".bz2"]', 'a[href$=".xz"]', 'a[href$=".lzma"]',
            'a[href$=".exe"]', 'a[href$=".msi"]', 'a[href$=".dmg"]', 'a[href$=".iso"]',
            'a[href$=".pdf"]', 'a[href$=".doc"]', 'a[href$=".docx"]',
            'a[href$=".xls"]', 'a[href$=".xlsx"]', 'a[href$=".ppt"]', 'a[href$=".pptx"]'
        ]

        # 如果提供了自定义选择器，则使用它们
        self.comment_selectors = comment_selectors or self.default_comment_selectors
        self.attachment_selectors = attachment_selectors or self.default_attachment_selectors

        # 用于检测的内容关键词
        self.comment_keywords = ['评论', '回复', '留言', '讨论', 'comment', 'reply', 'review', 'feedback']
        self.attachment_keywords = ['下载', '附件', '资源', '文件', 'download', 'attachment', 'file', 'upload']

        # 日志记录器
        self.logger = logger or Logger()

        # 信号发射器
        self.signals = SignalEmitter()

    def run(self):
        """线程执行函数"""
        try:
            self.logger.info(f"爬虫线程启动: {self.url}")

            if not self.url.startswith(('http://', 'https://')):
                self.logger.error("错误: URL必须以http://或https://开头")
                self.signals.crawl_completed.emit(False, "无效的URL")
                return

            if self.detect_mode:
                result = self._detect_page_structure()
                self.signals.crawl_completed.emit(True, json.dumps(result, ensure_ascii=False, indent=2))
            else:
                self._start_crawling()

        except Exception as e:
            self.logger.critical(f"爬取过程中发生严重错误: {str(e)}")
            self.signals.crawl_completed.emit(False, str(e))

    def _start_crawling(self):
        """开始正常爬取流程"""
        self.logger.info(f"开始爬取: {self.url}")
        self.signals.progress_updated.emit(0)

        # 存储已访问的URL，避免重复爬取
        visited_urls = set()
        urls_to_visit = [self.url]

        page_count = 0
        comment_count = 0
        attachment_count = 0

        while urls_to_visit and page_count < self.max_pages and self.running:
            url = urls_to_visit.pop(0)

            if url in visited_urls:
                self.logger.debug(f"跳过已访问的页面: {url}")
                continue

            try:
                visited_urls.add(url)
                page_count += 1

                # 更新进度
                progress = int((page_count / self.max_pages) * 100)
                self.signals.progress_updated.emit(progress)

                self.logger.info(f"正在爬取页面 {page_count}/{self.max_pages}: {url}")

                # 发送HTTP请求
                self.logger.debug(f"发送HTTP请求: {url}")
                response = requests.get(url, headers=self.headers, timeout=10)
                if response.status_code != 200:
                    self.logger.warning(f"错误: 页面 {url} 返回状态码 {response.status_code}")
                    continue

                # 检测编码
                self.logger.debug(f"检测页面编码: {url}")
                encoding = chardet.detect(response.content)['encoding']
                if encoding:
                    response.encoding = encoding
                    self.logger.info(f"检测到页面编码: {encoding}")
                else:
                    self.logger.warning("警告: 无法检测页面编码，使用默认编码")

                # 解析HTML内容
                self.logger.debug(f"解析HTML内容: {url}")
                soup = BeautifulSoup(response.text, 'html.parser')

                # 提取评论
                comments = self._extract_comments(soup, url)
                comment_count += len(comments)
                self.logger.info(f"在页面 {url} 上找到 {len(comments)} 条评论")

                # 提取附件
                attachments = self._extract_attachments(soup, url)
                attachment_count += len(attachments)
                self.logger.info(f"在页面 {url} 上找到 {len(attachments)} 个附件")

                # 提取页面中的链接，用于后续爬取
                links = soup.find_all('a')
                self.logger.debug(f"在页面 {url} 上找到 {len(links)} 个链接")

                for link in links:
                    href = link.get('href')
                    if href:
                        # 构建完整URL
                        full_url = urljoin(url, href)

                        # 简单的深度控制 - 只爬取同一域名下的页面
                        if self._is_same_domain(url, full_url) and full_url not in visited_urls:
                            urls_to_visit.append(full_url)

                # 为避免被封IP，添加短暂延迟
                time.sleep(0.5)

            except Exception as e:
                self.logger.error(f"爬取页面 {url} 时出错: {str(e)}")

        self.logger.info(f"爬取完成! 共爬取 {page_count} 个页面，找到 {comment_count} 条评论，{attachment_count} 个附件")
        self.signals.crawl_completed.emit(True,
                                          f"共爬取 {page_count} 个页面，找到 {comment_count} 条评论，{attachment_count} 个附件")

    def _detect_page_structure(self):
        """检测页面结构，自动识别评论区和附件"""
        self.logger.info(f"开始检测页面结构: {self.url}")

        try:
            # 发送HTTP请求
            self.logger.debug(f"发送HTTP请求进行结构检测: {self.url}")
            response = requests.get(self.url, headers=self.headers, timeout=10)
            if response.status_code != 200:
                self.logger.error(f"错误: 页面返回状态码 {response.status_code}")
                return {}

            # 检测编码
            self.logger.debug(f"检测页面编码: {self.url}")
            encoding = chardet.detect(response.content)['encoding']
            if encoding:
                response.encoding = encoding
                self.logger.info(f"检测到页面编码: {encoding}")
            else:
                self.logger.warning("警告: 无法检测页面编码，使用默认编码")

            # 解析HTML内容
            self.logger.debug(f"解析HTML内容进行结构检测: {self.url}")
            soup = BeautifulSoup(response.text, 'html.parser')

            # 检测评论区
            comment_results = {}
            self.logger.info("开始检测评论区结构...")
            for selector in self.comment_selectors:
                self.logger.debug(f"尝试评论选择器: {selector}")
                elements = soup.select(selector)
                if elements:
                    # 计算包含关键词的元素数量
                    keyword_matches = 0
                    for element in elements:
                        text = element.get_text(strip=True).lower()
                        if any(keyword.lower() in text for keyword in self.comment_keywords):
                            keyword_matches += 1

                    # 计算平均文本长度（排除过短的内容）
                    valid_texts = [element.get_text(strip=True) for element in elements if
                                   len(element.get_text(strip=True)) > 10]
                    avg_length = sum(len(t) for t in valid_texts) / max(1, len(valid_texts)) if valid_texts else 0

                    # 计算文本密度（每100个字符中的单词数）
                    text_density = 0
                    if valid_texts:
                        total_words = sum(len(t.split()) for t in valid_texts)
                        total_chars = sum(len(t) for t in valid_texts)
                        text_density = (total_words / max(1, total_chars)) * 100

                    comment_results[selector] = {
                        'count': len(elements),
                        'keyword_matches': keyword_matches,
                        'avg_length': avg_length,
                        'text_density': text_density,
                        'sample': elements[0].get_text(strip=True)[:200] if elements else ""
                    }

                    self.logger.debug(f"选择器 {selector} 匹配 {len(elements)} 个元素")

            # 检测附件
            attachment_results = {}
            self.logger.info("开始检测附件区结构...")
            for selector in self.attachment_selectors:
                self.logger.debug(f"尝试附件选择器: {selector}")
                elements = soup.select(selector)
                if elements:
                    # 计算包含关键词的元素数量
                    keyword_matches = 0
                    for element in elements:
                        text = element.get_text(strip=True).lower()
                        if any(keyword.lower() in text for keyword in self.attachment_keywords):
                            keyword_matches += 1

                    # 计算有效附件链接数量
                    valid_attachments = 0
                    for element in elements:
                        href = element.get('href') or element.get('src')
                        if href and self._is_valid_attachment(href):
                            valid_attachments += 1

                    attachment_results[selector] = {
                        'count': len(elements),
                        'keyword_matches': keyword_matches,
                        'valid_attachments': valid_attachments,
                        'sample': [{'text': element.get_text(strip=True)[:50], 'href': element.get('href')} for element
                                   in elements[:3]]
                    }

                    self.logger.debug(f"选择器 {selector} 匹配 {len(elements)} 个附件元素")

            # 返回检测结果
            self.logger.info(
                f"结构检测完成: 评论选择器匹配 {len(comment_results)} 个，附件选择器匹配 {len(attachment_results)} 个")
            return {
                'comments': comment_results,
                'attachments': attachment_results
            }

        except Exception as e:
            self.logger.critical(f"检测页面结构时出错: {str(e)}")
            return {}

    def _extract_comments(self, soup, page_url):
        """使用多种选择器提取评论"""
        self.logger.debug(f"开始从 {page_url} 提取评论...")
        comments = []
        extracted_texts = set()  # 用于去重

        for selector in self.comment_selectors:
            elements = soup.select(selector)
            self.logger.debug(f"选择器 {selector} 在 {page_url} 上匹配 {len(elements)} 个元素")

            for element in elements:
                comment_text = element.get_text(strip=True)

                # 过滤空内容和重复内容
                if not comment_text or comment_text in extracted_texts:
                    continue

                # 过滤过短的内容（可能不是评论）
                if len(comment_text) < 5:
                    continue

                # 检查是否包含垃圾内容
                if self._is_spam_content(comment_text):
                    continue

                extracted_texts.add(comment_text)

                comment_data = {
                    'text': comment_text,
                    'url': page_url,
                    'timestamp': time.strftime('%Y-%m-%d %H:%M:%S'),
                    'selector': selector
                }

                self.signals.comment_found.emit(comment_data)
                comments.append(comment_data)
                self.logger.debug(f"找到评论: {comment_text[:30]}...")

        self.logger.debug(f"从 {page_url} 提取了 {len(comments)} 条有效评论")
        return comments

    def _extract_attachments(self, soup, page_url):
        """使用多种选择器提取附件"""
        self.logger.debug(f"开始从 {page_url} 提取附件...")
        attachments = []
        seen_urls = set()  # 用于去重

        for selector in self.attachment_selectors:
            elements = soup.select(selector)
            self.logger.debug(f"选择器 {selector} 在 {page_url} 上匹配 {len(elements)} 个元素")

            for element in elements:
                href = element.get('href') or element.get('src')
                if not href:
                    continue

                # 构建完整URL
                full_url = urljoin(page_url, href)

                # 检查是否是有效的附件URL
                if not self._is_valid_attachment(full_url):
                    continue

                # 检查是否重复
                if full_url in seen_urls:
                    continue

                seen_urls.add(full_url)

                # 从URL中提取文件名
                filename = self._get_filename_from_url(full_url)

                # 尝试从链接文本中提取更友好的文件名
                link_text = element.get_text(strip=True)
                if link_text and len(link_text) > 3 and len(link_text) < 50:
                    # 去除非法文件名的字符
                    safe_text = re.sub(r'[\\/:*?"<>|]', '_', link_text)
                    # 获取文件扩展名
                    ext = os.path.splitext(filename)[1]
                    filename = f"{safe_text}{ext}"

                attachment_data = {
                    'url': full_url,
                    'filename': filename,
                    'source_page': page_url,
                    'selector': selector,
                    'type': self._get_attachment_type(filename)
                }

                self.signals.attachment_found.emit(attachment_data)
                attachments.append(attachment_data)
                self.logger.debug(f"找到附件: {filename}")

        self.logger.debug(f"从 {page_url} 提取了 {len(attachments)} 个有效附件")
        return attachments

    def stop(self):
        """停止爬虫线程"""
        self.running = False
        self.logger.info("爬虫线程已停止")

    def _is_valid_attachment(self, url):
        """检查URL是否指向有效的附件"""
        valid_extensions = [
            # 压缩文件
            '.zip', '.rar', '.7z', '.tar', '.gz', '.bz2', '.xz', '.lzma',
            # 安装包
            '.exe', '.msi', '.dmg', '.iso',
            # 文档
            '.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx',
            # 其他可能的文件类型
            '.txt', '.csv', '.xml', '.json', '.html', '.htm',
            '.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp',
            '.mp3', '.wav', '.ogg', '.flac', '.mp4', '.avi', '.mov', '.mkv'
        ]

        # 检查URL是否以有效扩展名结尾
        return any(url.lower().endswith(ext) for ext in valid_extensions)

    def _get_filename_from_url(self, url):
        """从URL中提取文件名"""
        try:
            # 尝试从URL路径部分提取文件名
            path = urlparse(url).path
            filename = os.path.basename(path)

            # 如果没有扩展名，添加一个默认的
            if not os.path.splitext(filename)[1]:
                filename += '.unknown'

            return filename
        except:
            # 如果提取失败，生成一个基于时间戳的文件名
            filename = f"attachment_{int(time.time())}.unknown"
            self.logger.warning(f"无法从URL {url} 提取文件名，使用默认文件名: {filename}")
            return filename

    def _is_same_domain(self, url1, url2):
        """检查两个URL是否属于同一域名"""
        return urlparse(url1).netloc == urlparse(url2).netloc

    def _get_attachment_type(self, filename):
        """确定附件类型"""
        ext = os.path.splitext(filename)[1].lower()

        if ext in ['.zip', '.rar', '.7z', '.tar', '.gz', '.bz2', '.xz', '.lzma']:
            return '压缩文件'
        elif ext in ['.exe', '.msi', '.dmg', '.iso']:
            return '安装包'
        elif ext in ['.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx']:
            return '文档'
        elif ext in ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp']:
            return '图片'
        elif ext in ['.mp3', '.wav', '.ogg', '.flac']:
            return '音频'
        elif ext in ['.mp4', '.avi', '.mov', '.mkv']:
            return '视频'
        else:
            return '其他文件'

    def _is_spam_content(self, text):
        """检查是否是垃圾内容"""
        # 检查是否包含过多重复字符
        if len(set(text)) / max(1, len(text)) < 0.3:
            self.logger.debug(f"过滤垃圾内容(重复字符过多): {text[:30]}...")
            return True

        # 检查是否包含过多URL
        if len(re.findall(r'http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\\(\\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+',
                          text)) > 2:
            self.logger.debug(f"过滤垃圾内容(URL过多): {text[:30]}...")
            return True

        # 检查是否包含垃圾关键词
        spam_keywords = ['免费领取', '立即注册', '点击领取', '优惠活动', '现金奖励', '投资回报', '暴利', '刷单']
        if any(keyword in text for keyword in spam_keywords):
            self.logger.debug(f"过滤垃圾内容(包含垃圾关键词): {text[:30]}...")
            return True

        return False


class DownloadThread(threading.Thread):
    """下载线程，负责附件下载"""

    def __init__(self, url, save_path, filename, category=None, logger=None):
        super().__init__()
        self.url = url
        self.save_path = save_path
        self.filename = filename
        self.category = category or "其他"
        self.running = True
        self.headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
        }
        self.logger = logger or Logger()

        # 信号发射器
        self.signals = SignalEmitter()

    def run(self):
        """线程执行函数"""
        try:
            self.logger.info(f"开始下载: {self.filename}")

            # 确保保存目录存在
            category_dir = os.path.join(self.save_path, self.category)
            os.makedirs(category_dir, exist_ok=True)
            full_path = os.path.join(category_dir, self.filename)

            # 检查文件是否已存在
            if os.path.exists(full_path):
                self.logger.warning(f"文件已存在: {self.filename}")
                self.signals.download_completed.emit(self.filename, True, "文件已存在")
                return

            # 发送HTTP请求获取文件
            self.logger.debug(f"发送下载请求: {self.url}")
            response = requests.get(self.url, headers=self.headers, stream=True, timeout=30)
            if response.status_code != 200:
                self.logger.error(f"下载失败，状态码: {response.status_code}")
                self.signals.download_completed.emit(self.filename, False, f"下载失败，状态码: {response.status_code}")
                return

            # 获取文件大小
            total_size = int(response.headers.get('content-length', 0))
            downloaded_size = 0

            # 写入文件
            self.logger.debug(f"开始写入文件: {full_path}")
            with open(full_path, 'wb') as file:
                for chunk in response.iter_content(chunk_size=1024):
                    if not self.running:
                        # 删除未完成的文件
                        if os.path.exists(full_path):
                            os.remove(full_path)
                        self.logger.info(f"下载已取消: {self.filename}")
                        self.signals.download_completed.emit(self.filename, False, "下载已取消")
                        return

                    if chunk:
                        file.write(chunk)
                        downloaded_size += len(chunk)
                        # 发送进度更新
                        self.signals.download_progress.emit(self.filename, downloaded_size, total_size)

            self.logger.info(f"下载完成: {self.filename}")
            self.signals.download_completed.emit(self.filename, True, "下载成功")

        except Exception as e:
            self.logger.critical(f"下载 {self.filename} 时出错: {str(e)}")
            self.signals.download_completed.emit(self.filename, False, f"下载出错: {str(e)}")

    def stop(self):
        """停止下载线程"""
        self.running = False
        self.logger.info(f"停止下载线程: {self.filename}")


class StructureDetectionDialog(QDialog):
    """页面结构检测结果对话框"""

    def __init__(self, detection_result, parent=None):
        super().__init__(parent)
        self.setWindowTitle("页面结构检测结果")
        self.resize(900, 600)

        self.main_layout = QVBoxLayout(self)

        # 创建标签页
        self.tabs = QTabWidget()

        # 评论区检测结果
        self.comment_tab = QWidget()
        self.comment_layout = QVBoxLayout(self.comment_tab)
        self.comment_tree = QTreeWidget()
        self.comment_tree.setHeaderLabels(
            ["选择器", "匹配数量", "关键词匹配率", "平均长度", "文本密度", "评分", "示例内容"])
        self.comment_tree.setColumnWidth(0, 200)
        self.comment_tree.setColumnWidth(1, 80)
        self.comment_tree.setColumnWidth(2, 100)
        self.comment_tree.setColumnWidth(3, 80)
        self.comment_tree.setColumnWidth(4, 80)
        self.comment_tree.setColumnWidth(5, 60)
        self.comment_layout.addWidget(self.comment_tree)
        self.tabs.addTab(self.comment_tab, "评论区检测")

        # 附件区检测结果
        self.attachment_tab = QWidget()
        self.attachment_layout = QVBoxLayout(self.attachment_tab)
        self.attachment_tree = QTreeWidget()
        self.attachment_tree.setHeaderLabels(["选择器", "匹配数量", "关键词匹配率", "有效附件率", "评分", "示例链接"])
        self.attachment_tree.setColumnWidth(0, 200)
        self.attachment_tree.setColumnWidth(1, 80)
        self.attachment_tree.setColumnWidth(2, 100)
        self.attachment_tree.setColumnWidth(3, 100)
        self.attachment_tree.setColumnWidth(4, 60)
        self.attachment_layout.addWidget(self.attachment_tree)
        self.tabs.addTab(self.attachment_tab, "附件区检测")

        # 按钮布局
        self.button_layout = QHBoxLayout()
        self.apply_btn = QPushButton("应用推荐选择器")
        self.apply_btn.clicked.connect(self.accept)
        self.cancel_btn = QPushButton("取消")
        self.cancel_btn.clicked.connect(self.reject)
        self.button_layout.addWidget(self.apply_btn)
        self.button_layout.addWidget(self.cancel_btn)

        self.main_layout.addWidget(self.tabs)
        self.main_layout.addLayout(self.button_layout)

        # 填充检测结果
        self._populate_comment_results(detection_result.get('comments', {}))
        self._populate_attachment_results(detection_result.get('attachments', {}))

        # 自动选择最佳匹配
        self._select_best_matches()

    def _populate_comment_results(self, results):
        """填充评论区检测结果"""
        self.comment_tree.clear()

        # 计算综合评分并排序
        scored_results = []
        for selector, data in results.items():
            # 计算评分 (关键词匹配率 * 0.5 + 平均长度评分 * 0.3 + 文本密度评分 * 0.2)
            keyword_score = data['keyword_matches'] / max(1, data['count'])
            length_score = min(data['avg_length'] / 100, 1.0)  # 超过100字符得满分
            density_score = min(data['text_density'] / 20, 1.0)  # 超过20词/100字符得满分

            score = keyword_score * 0.5 + length_score * 0.3 + density_score * 0.2
            scored_results.append((selector, data, score))

        # 按评分排序
        scored_results.sort(key=lambda x: x[2], reverse=True)

        for selector, data, score in scored_results:
            item = QTreeWidgetItem(self.comment_tree)
            item.setText(0, selector)
            item.setText(1, str(data['count']))
            item.setText(2, f"{data['keyword_matches']}/{data['count']} ({keyword_score:.1%})")
            item.setText(3, f"{data['avg_length']:.1f}字符")
            item.setText(4, f"{data['text_density']:.1f}词/百字符")
            item.setText(5, f"{score:.2f}")
            item.setText(6, data['sample'][:150] + ("..." if len(data['sample']) > 150 else ""))

            # 设置数据，便于后续获取
            item.setData(0, Qt.UserRole, selector)

            # 根据评分设置颜色
            if score > 0.7:
                item.setForeground(0, QColor("green"))
            elif score > 0.4:
                item.setForeground(0, QColor("darkorange"))
            else:
                item.setForeground(0, QColor("red"))

    def _populate_attachment_results(self, results):
        """填充附件区检测结果"""
        self.attachment_tree.clear()

        # 计算综合评分并排序
        scored_results = []
        for selector, data in results.items():
            # 计算评分 (关键词匹配率 * 0.4 + 有效附件率 * 0.6)
            keyword_score = data['keyword_matches'] / max(1, data['count'])
            valid_score = data['valid_attachments'] / max(1, data['count'])

            score = keyword_score * 0.4 + valid_score * 0.6
            scored_results.append((selector, data, score))

        # 按评分排序
        scored_results.sort(key=lambda x: x[2], reverse=True)

        for selector, data, score in scored_results:
            item = QTreeWidgetItem(self.attachment_tree)
            item.setText(0, selector)
            item.setText(1, str(data['count']))
            item.setText(2, f"{data['keyword_matches']}/{data['count']} ({keyword_score:.1%})")
            item.setText(3, f"{data['valid_attachments']}/{data['count']} ({valid_score:.1%})")
            item.setText(4, f"{score:.2f}")

            # 构建示例链接文本
            sample_text = ""
            for sample in data.get('sample', [])[:3]:
                href = sample['href'] or ""
                href_display = href[:40] + ("..." if len(href) > 40 else "")
                sample_text += f"{sample['text']} ({href_display})\n"

            item.setText(5, sample_text.strip())

            # 设置数据，便于后续获取
            item.setData(0, Qt.UserRole, selector)

            # 根据评分设置颜色
            if score > 0.7:
                item.setForeground(0, QColor("green"))
            elif score > 0.4:
                item.setForeground(0, QColor("darkorange"))
            else:
                item.setForeground(0, QColor("red"))

    def _select_best_matches(self):
        """自动选择最佳匹配的选择器"""
        # 选择评论区最佳匹配
        if self.comment_tree.topLevelItemCount() > 0:
            self.comment_tree.topLevelItem(0).setSelected(True)

        # 选择附件区最佳匹配
        if self.attachment_tree.topLevelItemCount() > 0:
            self.attachment_tree.topLevelItem(0).setSelected(True)

    def get_selected_selectors(self):
        """获取用户选择的选择器"""
        comment_selectors = []
        for i in range(self.comment_tree.topLevelItemCount()):
            item = self.comment_tree.topLevelItem(i)
            if item.isSelected():
                comment_selectors.append(item.data(0, Qt.UserRole))

        attachment_selectors = []
        for i in range(self.attachment_tree.topLevelItemCount()):
            item = self.attachment_tree.topLevelItem(i)
            if item.isSelected():
                attachment_selectors.append(item.data(0, Qt.UserRole))

        return {
            'comments': ','.join(comment_selectors) if comment_selectors else None,
            'attachments': ','.join(attachment_selectors) if attachment_selectors else None
        }


class PresetSelectorDialog(QDialog):
    """选择器预设对话框"""

    def __init__(self, preset_type, parent=None):
        super().__init__(parent)
        self.setWindowTitle("选择器预设")
        self.resize(400, 300)

        self.preset_type = preset_type  # 'comments' 或 'attachments'
        self.selected_preset = None

        self.main_layout = QVBoxLayout(self)

        # 创建预设列表
        self.preset_list = QListWidget()

        # 添加预设
        self.presets = self._get_presets()

        # 添加预设到列表
        for preset_name in self.presets.keys():
            self.preset_list.addItem(preset_name)

        # 按钮布局
        self.button_layout = QHBoxLayout()
        self.select_btn = QPushButton("选择")
        self.select_btn.clicked.connect(self._on_select)
        self.cancel_btn = QPushButton("取消")
        self.cancel_btn.clicked.connect(self.reject)

        self.button_layout.addWidget(self.select_btn)
        self.button_layout.addWidget(self.cancel_btn)

        self.main_layout.addWidget(self.preset_list)
        self.main_layout.addLayout(self.button_layout)

    def _get_presets(self):
        """获取预设列表"""
        if self.preset_type == 'comments':
            return {
                "通用论坛": "div.comment,div.reply,div.post-message,div.thread-message",
                "新闻网站": "div.comment,div.comment-item,div.comment-body,div.comment-content",
                "社交媒体": "div.post,div.status,div.comment,div.comment-text",
                "电商评论": "div.review,div.review-item,div.comment,div.review-content",
                "自定义": ""
            }
        else:  # attachments
            return {
                "压缩文件": "a[href$='.zip'],a[href$='.rar'],a[href$='.7z'],a[href$='.tar'],a[href$='.gz']",
                "文档文件": "a[href$='.pdf'],a[href$='.doc'],a[href$='.docx'],a[href$='.xls'],a[href$='.xlsx'],a[href$='.ppt'],a[href$='.pptx']",
                "图片文件": "a[href$='.jpg'],a[href$='.jpeg'],a[href$='.png'],a[href$='.gif'],a[href$='.bmp'],a[href$='.webp']",
                "所有文件": "a[href$='.zip'],a[href$='.rar'],a[href$='.7z'],a[href$='.tar'],a[href$='.gz'],a[href$='.pdf'],a[href$='.doc'],a[href$='.docx'],a[href$='.jpg'],a[href$='.png']",
                "自定义": ""
            }

    def _on_select(self):
        """选择预设"""
        selected_items = self.preset_list.selectedItems()
        if not selected_items:
            QMessageBox.warning(self, "警告", "请选择一个预设")
            return

        preset_name = selected_items[0].text()
        self.selected_preset = self.presets.get(preset_name, "")
        self.accept()

    def get_selected_preset(self):
        """获取选中的预设"""
        return self.selected_preset


class AdvancedWebCrawler(QMainWindow):
    """高级网页爬虫主窗口"""

    def __init__(self):
        super().__init__()
        self.setWindowTitle("高级网页爬虫工具")
        self.resize(1200, 800)

        # 初始化变量
        self.crawler_thread = None
        self.download_threads = {}
        self.save_directory = os.path.join(os.getcwd(), "downloads")
        self.comment_selectors = None
        self.attachment_selectors = None

        # 创建日志记录器
        self.logger = Logger()

        # 创建主界面
        self._create_main_interface()

        # 连接信号和槽
        self._connect_signals()

        # 加载设置
        self._load_settings()

    def _create_main_interface(self):
        """创建主界面"""
        # 创建中央部件
        central_widget = QWidget()
        self.setCentralWidget(central_widget)

        # 创建主布局
        main_layout = QVBoxLayout(central_widget)

        # 创建工具栏
        self._create_toolbar()

        # 创建状态栏
        self.statusBar = QStatusBar()
        self.setStatusBar(self.statusBar)
        self.status_label = QLabel("就绪")
        self.statusBar.addWidget(self.status_label)
        self.progress_bar = QProgressBar()
        self.progress_bar.setMaximumWidth(200)
        self.progress_bar.hide()
        self.statusBar.addPermanentWidget(self.progress_bar)

        # 创建选项卡控件
        self.tabs = QTabWidget()

        # 爬取设置选项卡
        self.setup_tab = QWidget()
        self.setup_layout = QVBoxLayout(self.setup_tab)

        # URL输入
        url_layout = QHBoxLayout()
        url_label = QLabel("URL:")
        self.url_input = QLineEdit("https://example.com")
        url_button = QPushButton("开始爬取")
        url_button.clicked.connect(self._start_crawling)
        url_layout.addWidget(url_label)
        url_layout.addWidget(self.url_input, 1)
        url_layout.addWidget(url_button)

        # 爬取设置
        settings_group = QGroupBox("爬取设置")
        settings_layout = QFormLayout()

        self.max_pages_input = QLineEdit("10")
        settings_layout.addRow("最大爬取页数:", self.max_pages_input)

        detect_button = QPushButton("检测页面结构")
        detect_button.clicked.connect(self._detect_structure)
        settings_layout.addRow("智能检测:", detect_button)

        # 评论选择器
        comment_layout = QHBoxLayout()
        self.comment_selector_input = QLineEdit()
        preset_comment_btn = QPushButton("预设")
        preset_comment_btn.clicked.connect(lambda: self._select_preset('comments'))
        comment_layout.addWidget(self.comment_selector_input, 1)
        comment_layout.addWidget(preset_comment_btn)
        settings_layout.addRow("评论选择器:", comment_layout)

        # 附件选择器
        attachment_layout = QHBoxLayout()
        self.attachment_selector_input = QLineEdit()
        preset_attachment_btn = QPushButton("预设")
        preset_attachment_btn.clicked.connect(lambda: self._select_preset('attachments'))
        attachment_layout.addWidget(self.attachment_selector_input, 1)
        attachment_layout.addWidget(preset_attachment_btn)
        settings_layout.addRow("附件选择器:", attachment_layout)

        settings_group.setLayout(settings_layout)

        # 日志级别设置
        log_levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
        log_layout = QHBoxLayout()
        log_label = QLabel("日志级别:")
        self.log_level_combobox = QComboBox()
        self.log_level_combobox.addItems(log_levels)
        self.log_level_combobox.setCurrentText("INFO")
        self.log_level_combobox.currentTextChanged.connect(self._set_log_level)
        log_layout.addWidget(log_label)
        log_layout.addWidget(self.log_level_combobox)

        # 添加到设置布局
        self.setup_layout.addLayout(url_layout)
        self.setup_layout.addWidget(settings_group)
        self.setup_layout.addLayout(log_layout)
        self.setup_layout.addStretch()

        # 评论选项卡
        self.comments_tab = QWidget()
        self.comments_layout = QVBoxLayout(self.comments_tab)
        self.comments_tree = QTreeWidget()
        self.comments_tree.setHeaderLabels(["评论内容", "来源页面", "选择器", "时间"])
        self.comments_tree.setColumnWidth(0, 400)
        self.comments_tree.setColumnWidth(1, 300)
        self.comments_tree.setColumnWidth(2, 150)
        self.comments_tree.setColumnWidth(3, 150)
        self.comments_layout.addWidget(self.comments_tree)

        # 附件选项卡
        self.attachments_tab = QWidget()
        self.attachments_layout = QVBoxLayout(self.attachments_tab)

        # 附件列表
        self.attachments_tree = QTreeWidget()
        self.attachments_tree.setHeaderLabels(["文件名", "类型", "来源页面", "状态", "下载进度"])
        self.attachments_tree.setColumnWidth(0, 300)
        self.attachments_tree.setColumnWidth(1, 80)
        self.attachments_tree.setColumnWidth(2, 300)
        self.attachments_tree.setColumnWidth(3, 80)
        self.attachments_tree.setColumnWidth(4, 150)
        self.attachments_layout.addWidget(self.attachments_tree)

        # 下载控制按钮
        download_buttons_layout = QHBoxLayout()
        self.download_all_btn = QPushButton("全部下载")
        self.download_all_btn.clicked.connect(self._download_all_attachments)
        self.cancel_all_btn = QPushButton("取消全部")
        self.cancel_all_btn.clicked.connect(self._cancel_all_downloads)
        self.open_dir_btn = QPushButton("打开下载目录")
        self.open_dir_btn.clicked.connect(self._open_download_directory)

        download_buttons_layout.addWidget(self.download_all_btn)
        download_buttons_layout.addWidget(self.cancel_all_btn)
        download_buttons_layout.addWidget(self.open_dir_btn)
        self.attachments_layout.addLayout(download_buttons_layout)

        # 日志选项卡
        self.log_tab = QWidget()
        self.log_layout = QVBoxLayout(self.log_tab)
        self.log_text = QTextEdit()
        self.log_text.setReadOnly(True)
        self.log_text.setAcceptRichText(True)
        self.log_layout.addWidget(self.log_text)

        # 添加选项卡
        self.tabs.addTab(self.setup_tab, "爬取设置")
        self.tabs.addTab(self.comments_tab, "评论")
        self.tabs.addTab(self.attachments_tab, "附件")
        self.tabs.addTab(self.log_tab, "日志")

        # 添加到主布局
        main_layout.addWidget(self.tabs)

    def _create_toolbar(self):
        """创建工具栏"""
        toolbar = QToolBar("工具栏")
        self.addToolBar(toolbar)

        # 添加动作
        save_action = QAction("保存配置", self)
        save_action.triggered.connect(self._save_settings)
        toolbar.addAction(save_action)

        load_action = QAction("加载配置", self)
        load_action.triggered.connect(self._load_settings)
        toolbar.addAction(load_action)

        toolbar.addSeparator()

        about_action = QAction("关于", self)
        about_action.triggered.connect(self._show_about)
        toolbar.addAction(about_action)

    def _connect_signals(self):
        """连接信号和槽"""
        # 设置日志记录器的日志输出控件
        self.logger.set_log_widget(self._log_message)

    def _start_crawling(self):
        """开始爬取"""
        url = self.url_input.text().strip()
        if not url:
            QMessageBox.warning(self, "警告", "请输入URL")
            return

        try:
            max_pages = int(self.max_pages_input.text())
            if max_pages <= 0:
                raise ValueError
        except ValueError:
            QMessageBox.warning(self, "警告", "最大页数必须是正整数")
            return

        # 获取选择器
        comment_selectors = self.comment_selector_input.text().strip()
        if comment_selectors:
            comment_selectors = comment_selectors.split(',')
        else:
            comment_selectors = None

        attachment_selectors = self.attachment_selector_input.text().strip()
        if attachment_selectors:
            attachment_selectors = attachment_selectors.split(',')
        else:
            attachment_selectors = None

        # 清空之前的结果
        self.comments_tree.clear()
        self.attachments_tree.clear()

        # 创建并启动爬虫线程
        self.crawler_thread = CrawlerThread(
            url=url,
            comment_selectors=comment_selectors,
            attachment_selectors=attachment_selectors,
            max_pages=max_pages,
            logger=self.logger
        )

        # 连接信号
        self.crawler_thread.signals.progress_updated.connect(self._update_progress)
        self.crawler_thread.signals.comment_found.connect(self._add_comment)
        self.crawler_thread.signals.attachment_found.connect(self._add_attachment)
        self.crawler_thread.signals.crawl_completed.connect(self._crawl_completed)
        self.crawler_thread.signals.log_message.connect(self._log_message)

        # 禁用开始按钮，显示进度条
        self.progress_bar.show()
        self.progress_bar.setValue(0)
        self.status_label.setText("正在爬取...")

        # 启动线程
        self.crawler_thread.start()

    def _detect_structure(self):
        """检测页面结构"""
        url = self.url_input.text().strip()
        if not url:
            QMessageBox.warning(self, "警告", "请输入URL")
            return

        # 创建并启动爬虫线程进行结构检测
        self.crawler_thread = CrawlerThread(
            url=url,
            detect_mode=True,
            logger=self.logger
        )

        # 连接信号
        self.crawler_thread.signals.crawl_completed.connect(self._structure_detection_completed)
        self.crawler_thread.signals.log_message.connect(self._log_message)

        # 禁用开始按钮，显示进度条
        self.progress_bar.show()
        self.progress_bar.setValue(0)
        self.status_label.setText("正在检测页面结构...")

        # 启动线程
        self.crawler_thread.start()

    def _structure_detection_completed(self, success, result):
        """页面结构检测完成"""
        self.progress_bar.hide()

        if not success:
            self.status_label.setText("结构检测失败")
            QMessageBox.critical(self, "错误", f"结构检测失败: {result}")
            return

        self.status_label.setText("结构检测完成")

        # 解析结果
        try:
            detection_result = json.loads(result)
        except json.JSONDecodeError:
            self.logger.error(f"无法解析结构检测结果: {result}")
            QMessageBox.critical(self, "错误", "无法解析结构检测结果")
            return

        # 显示检测结果对话框
        dialog = StructureDetectionDialog(detection_result, self)
        if dialog.exec_():
            # 应用用户选择的选择器
            selected = dialog.get_selected_selectors()
            if selected['comments']:
                self.comment_selector_input.setText(selected['comments'])
            if selected['attachments']:
                self.attachment_selector_input.setText(selected['attachments'])

    def _select_preset(self, preset_type):
        """选择预设选择器"""
        dialog = PresetSelectorDialog(preset_type, self)
        if dialog.exec_():
            preset = dialog.get_selected_preset()
            if preset is not None:
                if preset_type == 'comments':
                    self.comment_selector_input.setText(preset)
                else:
                    self.attachment_selector_input.setText(preset)

    def _update_progress(self, progress):
        """更新进度条"""
        self.progress_bar.setValue(progress)

    def _add_comment(self, comment_data):
        """添加评论到评论树"""
        item = QTreeWidgetItem(self.comments_tree)
        item.setText(0, comment_data['text'])
        item.setText(1, comment_data['url'])
        item.setText(2, comment_data['selector'])
        item.setText(3, comment_data['timestamp'])

        # 自动展开树
        self.comments_tree.expandAll()

    def _add_attachment(self, attachment_data):
        """添加附件到附件树"""
        item = QTreeWidgetItem(self.attachments_tree)
        item.setText(0, attachment_data['filename'])
        item.setText(1, attachment_data['type'])
        item.setText(2, attachment_data['source_page'])
        item.setText(3, "待下载")

        # 保存附件数据
        item.setData(0, Qt.UserRole, attachment_data)

        # 自动展开树
        self.attachments_tree.expandAll()

    def _crawl_completed(self, success, message):
        """爬取完成"""
        self.progress_bar.hide()

        if success:
            self.status_label.setText("爬取完成")
            self.logger.info(f"爬取完成: {message}")
            QMessageBox.information(self, "成功", f"爬取完成!\n{message}")
        else:
            self.status_label.setText("爬取失败")
            self.logger.error(f"爬取失败: {message}")
            QMessageBox.critical(self, "错误", f"爬取失败: {message}")

    def _download_all_attachments(self):
        """下载所有附件"""
        if not os.path.exists(self.save_directory):
            os.makedirs(self.save_directory)

        for i in range(self.attachments_tree.topLevelItemCount()):
            item = self.attachments_tree.topLevelItem(i)
            status = item.text(3)

            # 只下载待下载的附件
            if status == "待下载":
                attachment_data = item.data(0, Qt.UserRole)
                self._start_download(attachment_data, item)

    def _start_download(self, attachment_data, tree_item):
        """开始下载附件"""
        url = attachment_data['url']
        filename = attachment_data['filename']
        category = attachment_data['type']

        # 创建下载线程
        download_thread = DownloadThread(
            url=url,
            save_path=self.save_directory,
            filename=filename,
            category=category,
            logger=self.logger
        )

        # 保存线程引用
        self.download_threads[filename] = download_thread

        # 更新UI状态
        tree_item.setText(3, "下载中")

        # 创建进度条
        progress_bar = QProgressBar()
        progress_bar.setRange(0, 100)
        progress_bar.setValue(0)
        self.attachments_tree.setItemWidget(tree_item, 4, progress_bar)

        # 连接信号
        download_thread.signals.download_progress.connect(
            lambda name, d, t: self._update_download_progress(name, d, t, progress_bar)
        )
        download_thread.signals.download_completed.connect(
            lambda name, s, m: self._download_completed(name, s, m, tree_item)
        )
        download_thread.signals.log_message.connect(self._log_message)

        # 启动线程
        download_thread.start()

    def _update_download_progress(self, filename, downloaded, total, progress_bar):
        """更新下载进度"""
        if total > 0:
            progress = int((downloaded / total) * 100)
            progress_bar.setValue(progress)

    def _download_completed(self, filename, success, message, tree_item):
        """下载完成"""
        if success:
            tree_item.setText(3, "已完成")
            self.logger.info(f"下载完成: {filename}")
        else:
            tree_item.setText(3, "失败")
            self.logger.error(f"下载失败: {filename} - {message}")

        # 移除线程引用
        if filename in self.download_threads:
            del self.download_threads[filename]

    def _cancel_all_downloads(self):
        """取消所有下载"""
        for filename, thread in list(self.download_threads.items()):
            thread.stop()
            del self.download_threads[filename]

        # 更新UI状态
        for i in range(self.attachments_tree.topLevelItemCount()):
            item = self.attachments_tree.topLevelItem(i)
            status = item.text(3)
            if status == "下载中":
                item.setText(3, "已取消")

    def _open_download_directory(self):
        """打开下载目录"""
        if not os.path.exists(self.save_directory):
            os.makedirs(self.save_directory)

        # 根据操作系统打开目录
        if os.name == 'nt':  # Windows
            os.startfile(self.save_directory)
        elif os.name == 'posix':  # macOS/Linux
            os.system(f'open "{self.save_directory}"')

    def _log_message(self, level, message):
        """记录日志消息"""
        # 根据日志级别设置颜色
        colors = {
            LogLevel.DEBUG: "#888888",
            LogLevel.INFO: "#000000",
            LogLevel.WARNING: "#FF8C00",
            LogLevel.ERROR: "#FF0000",
            LogLevel.CRITICAL: "#8B0000"
        }

        color = colors.get(level, "#000000")
        self.log_text.append(f'<span style="color:{color}">{message}</span>')

    def _set_log_level(self, level):
        """设置日志级别"""
        # 启用当前级别及以上的日志
        levels = [LogLevel.DEBUG, LogLevel.INFO, LogLevel.WARNING, LogLevel.ERROR, LogLevel.CRITICAL]
        level_index = levels.index(level)

        for i, lvl in enumerate(levels):
            self.logger.set_level_enabled(lvl, i >= level_index)

        self.logger.info(f"日志级别已设置为: {level}")

    def _save_settings(self):
        """保存设置"""
        settings = {
            'url': self.url_input.text(),
            'max_pages': self.max_pages_input.text(),
            'comment_selectors': self.comment_selector_input.text(),
            'attachment_selectors': self.attachment_selector_input.text(),
            'log_level': self.log_level_combobox.currentText(),
            'save_directory': self.save_directory
        }

        file_path, _ = QFileDialog.getSaveFileName(
            self, "保存配置", os.path.expanduser("~"), "JSON文件 (*.json)"
        )

        if file_path:
            try:
                with open(file_path, 'w', encoding='utf-8') as f:
                    json.dump(settings, f, ensure_ascii=False, indent=2)
                self.logger.info(f"配置已保存到: {file_path}")
                QMessageBox.information(self, "成功", f"配置已保存到: {file_path}")
            except Exception as e:
                self.logger.error(f"保存配置失败: {str(e)}")
                QMessageBox.critical(self, "错误", f"保存配置失败: {str(e)}")

    def _load_settings(self):
        """加载设置"""
        file_path, _ = QFileDialog.getOpenFileName(
            self, "加载配置", os.path.expanduser("~"), "JSON文件 (*.json)"
        )

        if file_path:
            try:
                with open(file_path, 'r', encoding='utf-8') as f:
                    settings = json.load(f)

                self.url_input.setText(settings.get('url', ""))
                self.max_pages_input.setText(settings.get('max_pages', "10"))
                self.comment_selector_input.setText(settings.get('comment_selectors', ""))
                self.attachment_selector_input.setText(settings.get('attachment_selectors', ""))
                self.save_directory = settings.get('save_directory', os.path.join(os.getcwd(), "downloads"))

                log_level = settings.get('log_level', "INFO")
                if log_level in ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]:
                    self.log_level_combobox.setCurrentText(log_level)
                    self._set_log_level(log_level)

                self.logger.info(f"配置已从 {file_path} 加载")
                QMessageBox.information(self, "成功", f"配置已从 {file_path} 加载")
            except Exception as e:
                self.logger.error(f"加载配置失败: {str(e)}")
                QMessageBox.critical(self, "错误", f"加载配置失败: {str(e)}")

    def _show_about(self):
        """显示关于对话框"""
        QMessageBox.about(
            self,
            "关于高级网页爬虫",
            "高级网页爬虫工具 v1.0\n\n"
            "一个功能强大的网页爬虫工具，支持评论和附件提取、智能页面结构检测、详细日志记录等功能。\n\n"
            "© 2025 高级网页爬虫开发团队"
        )

    def closeEvent(self, event):
        """关闭窗口事件"""
        # 停止所有运行中的线程
        if self.crawler_thread and self.crawler_thread.is_alive():
            self.crawler_thread.stop()

        for filename, thread in list(self.download_threads.items()):
            thread.stop()

        event.accept()


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = AdvancedWebCrawler()
    window.show()
    sys.exit(app.exec_())